package com.c6906.vdam.das.config.redis;

import com.alibaba.fastjson.JSON;
import com.c6906.vdam.das.common.DateUtils;
import com.c6906.vdam.das.model.dto.FunctionalAreaDTO;
import com.c6906.vdam.das.model.entity.TerminalBusinessInfo;
import com.c6906.vdam.das.model.entity.TerminalData;
import com.c6906.vdam.das.model.entity.VehiclesLast;
import com.c6906.vdam.das.model.entity.VehiclesUnfixDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

@Component
public class RedisUtils {

    private static final Logger logger = LoggerFactory.getLogger(RedisUtils.class);

    private static final String UNFIX_DETAIL_PRE = "UnfixDetail:";
    private static final String LAST_HISTORY_DATA_PRE = "LastHistoryData";
    private static final String PRE_REDIS_KEY = "SN";
    private static final String TERMINAL_HEARTBEAT_KEY = "LastLoc:";
    private static final String PRE_GPS_DATA_REDIS_KEY = "GPS_DATA";
    private static final String FUNC_AREA_KEY = "Fence:";

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //-------------------------- 基础操作 --------------------------
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

    public void setExpire(String key, String value, long timeout, java.util.concurrent.TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }

    public boolean exists(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    //-------------------------- 高级操作 --------------------------

    public TerminalBusinessInfo terminalInfoIsExistFormRedis(String sn) {
        Object info = redisTemplate.opsForValue().get(sn);
        if (info instanceof String) {
            try {
                return JSON.parseObject((String) info, TerminalBusinessInfo.class);
            } catch (Exception e) {
                logger.error("terminalInfoIsExistFormRedis JSON解析异常", e);
            }
        }
        return new TerminalBusinessInfo();
    }

    //-------------------------- 分布式锁 --------------------------
    public boolean tryLock(String lockKey, String requestId, int expireTime) {
        try {
            DefaultRedisScript<Long> script = new DefaultRedisScript<>(
                    "if redis.call('set', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]) then return 1 else return 0 end",
                    Long.class
            );

            Long result = stringRedisTemplate.execute(
                    script,
                    Collections.singletonList(lockKey),
                    requestId,
                    String.valueOf(expireTime)
            );

            // IDE提示"result != null"总是true，说明Redis操作异常已被Spring处理
            return result != null && result == 1L;
        } catch (RedisConnectionFailureException e) {
            logger.error("Redis连接失败，获取锁失败", e);
            return false;
        }
    }

    public boolean releaseLock(String lockKey, String requestId) {
        try {
            String script =
                    "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                            "   return redis.call('del', KEYS[1]) " +
                            "else " +
                            "   return 0 " +
                            "end";

            Long result = stringRedisTemplate.execute(
                    new DefaultRedisScript<>(script, Long.class),
                    Collections.singletonList(lockKey),
                    requestId
            );

            return result != null && result == 1L;
        } catch (RedisConnectionFailureException e) {
            logger.error("Redis连接失败，释放锁失败", e);
            return false;
        }
    }

    //-------------------------- 集合操作 --------------------------
    public void setAdd(String key, String value) {
        stringRedisTemplate.opsForSet().add(key, value);
    }

    //-------------------------- 复杂对象操作 --------------------------
    public VehiclesLast getOneTerminalStatuses(String terminalSn) {
        String gpsKey = PRE_GPS_DATA_REDIS_KEY + terminalSn;
        VehiclesLast vehiclesLast = new VehiclesLast();

        Object gpsData = redisTemplate.opsForValue().get(gpsKey);
        if (gpsData instanceof String) {
            try {
                vehiclesLast = JSON.parseObject((String) gpsData, VehiclesLast.class);
                String heartbeatKey = TERMINAL_HEARTBEAT_KEY + terminalSn;
                String lastHeartbeat = (String) stringRedisTemplate.opsForHash().get(heartbeatKey, "time");
                if (lastHeartbeat != null) {
                    vehiclesLast.setStrLastHeartbeatTime(
                            String.valueOf(DateUtils.strToDate(lastHeartbeat).getTime())
                    );
                }
            } catch (Exception e) {
                logger.error("getOneTerminalStatuses解析异常", e);
            }
        }
        return vehiclesLast;
    }

    @Cacheable(value = "functionalAreas", key = "#root.methodName")
    public List<FunctionalAreaDTO> getFunctionalAreas() {
        Set<String> fenceIds = stringRedisTemplate.opsForSet().members("FenceIds");
        assert fenceIds != null;
        return fenceIds.stream().map(id -> {
            String value = stringRedisTemplate.opsForValue().get(FUNC_AREA_KEY + id);
            try {
                return JSON.parseObject(value, FunctionalAreaDTO.class);
            } catch (Exception e) {
                logger.warn("禁停区数据解析失败: id={}", id, e);
                return null;
            }
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

    public void saveLastDatasForHistory(int num, List<TerminalData> terminalDataList) {
        for (TerminalData terminalData : terminalDataList) {
            String lastHistory = PRE_REDIS_KEY + terminalData.getTerminalSn() + LAST_HISTORY_DATA_PRE;
            redisTemplate.opsForList().leftPush(lastHistory, JSON.toJSONString(terminalData));
            redisTemplate.opsForList().trim(lastHistory, 0, num - 1);
        }
    }

    public List<String> getLastDatasForHistory(String terminalSn) {
        String lastHistory = PRE_REDIS_KEY + terminalSn + LAST_HISTORY_DATA_PRE;
        return stringRedisTemplate.opsForList().range(lastHistory, 0, -1);
    }

    //-------------------------- 其他操作 --------------------------
    public VehiclesUnfixDetail getVehiclesUnfixDetail(String terminalSn) {
        Object data = redisTemplate.opsForValue().get(UNFIX_DETAIL_PRE + terminalSn);
        if (data instanceof String) {
            try {
                return JSON.parseObject((String) data, VehiclesUnfixDetail.class);
            } catch (Exception e) {
                logger.error("getVehiclesUnfixDetail解析异常", e);
            }
        }
        return null;
    }

    public void delByPrefix(String prefix) {
        deleteByPattern(prefix);
    }

    private void deleteByPattern(String patternPrefix) {
        String pattern = patternPrefix + "*";
        Set<String> keys = scanKeys(pattern);
        if (!keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
    }

    private Set<String> scanKeys(String pattern) {
        return redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            Set<String> keys = new HashSet<>();
            ScanOptions options = ScanOptions.scanOptions()
                    .match(pattern)
                    .count(1000)
                    .build();
            try (Cursor<byte[]> cursor = connection.scan(options)) {
                while (cursor.hasNext()) {
                    keys.add(new String(cursor.next(), StandardCharsets.UTF_8));
                }
            } catch (Exception e) {
                logger.error("scanKeys异常, pattern: {}", pattern, e);
            }
            return keys;
        });
    }
}